package com.rtsapp.server.logger.appender;

import com.rtsapp.server.logger.spi.LogLog;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 *
 * 一个周期性的文本文件日志输出
 *  RollingFileAppender不是线程安全的
 *  1. 直接使用FileWriter进行文件输出( 在实际测试中它快于BufferedWriter, NIO的FileChannel, FileChannel可能适合写二进制文件 )
 */
public class RollingFileAppender implements ILoggerAppender<String>{

    /**
     * 一天的毫秒数
     */
    private static final long DAY_TOTAL_MSEC =  24 * 60 * 60 * 1000;
    /**
     * 每多少次刷新一次日志到文件
     */
    private static final int  DEFAULT_FLUSH_WRITE_TIMES = 100;
    /**
     * 单个日志文件的最大大小
     */
    private static final int  DEFAULT_MAX_LOG_FILE_LENGTH = 1024 * 1024 * 1024; //默认设置为1G

    /**
     * 最大的单日日志顺序号10000
     */
    private static final int  DEFAULT_MAX_LOG_SEQ_NO=  10000;


    private final String fileName;
    private final String fileNamePattern ;
    private final String dateFormat;
    private final LogggerRollingCalendar calendar;

    private volatile Writer loggerWriter = null;
    private volatile long lastCreateLogTime = 0;
    /**
     * 当前写入的日志文件的总大小
     * 该大小初始为日志文件大小，每写入一个字符串，累加上长度(长度并不是以UTF8算出的长度, 而是UTF16算出的长度), 因此长度并不是精准的，这个没有问题, 这里不需要精准, 只需要单个日志文件不要太大，解决日志文件性能问题
     */
    private volatile long totalLogSize = 0;

    private long nextDayStartMills = 0;
    private long totalWriteTimes = 0 ;
    private long flushWriteTimes = 0;

    private long prevTimeMillis = 0;
    private volatile boolean isClose  = false;

    public RollingFileAppender( String fileName, String dateFormat ){

        LogggerRollingCalendar calendar = new LogggerRollingCalendar();
        calendar.setType(LogggerRollingCalendar.TOP_OF_DAY, 0);

        this.fileName = fileName;
        this.dateFormat = dateFormat;
        this.calendar = calendar;

        // 处理日志文件名后缀: 必须存在，且不能是第一个或最后一个，否则在后面+
        int suffixIndex = fileName.lastIndexOf( ".");
        if( suffixIndex > 0  && suffixIndex < fileName.length() - 1 ){
            this.fileNamePattern =  fileName.substring( 0, suffixIndex  ) + "-%s" + fileName.substring( suffixIndex );
        }else{
            this.fileNamePattern = fileName + "-%s";
        }

        //初始化当前已写日志的大小
        try {
            File logFile = new File(this.fileName);
            if (logFile.exists()) {
                totalLogSize = logFile.length();
            }
        }catch( Throwable ex ){
            LogLog.error( "RollingFileAppender创建时,获取Log文件大小出错", ex );
        }

    }


    @Override
    public void close() {
        if( ! isClose ) {
            synchronized ( this ) {
                if( ! isClose ) {
                    try {
                        if( loggerWriter != null ) {
                            loggerWriter.flush();
                            loggerWriter.close();
                        }
                        isClose = true;
                    } catch (IOException e) {
                        LogLog.error("RollingFileAppender close error", e);
                    }
                }
            }
        }
    }


    @Override
    public void appendLog( String log ){

        long timeMillis = System.currentTimeMillis();

        // 先累计日志长度
        // 注意: 这个代码, 包括下面这两个代码顺序不能调换, totalLogSize 可能在因时间周期变动,创建一个新的日志文件后变为0
        totalLogSize += log.length() ; // 长度为字符长度 * 2

        if( timeMillis >= nextDayStartMills ){
            gotoNextPeriod( false );
        }

        if( totalLogSize > DEFAULT_MAX_LOG_FILE_LENGTH ){
            gotoNextPeriod( true );
        }

        if( loggerWriter == null ){
            LogLog.error( "loggerWriter 为null, 不能记录日志, 下面是要记录的日志" );
            LogLog.error( log );
            return;
        }

        try {
            loggerWriter.write( log );

            totalWriteTimes++;
            flushWriteTimes++;
            if( flushWriteTimes >= DEFAULT_FLUSH_WRITE_TIMES || (timeMillis - prevTimeMillis) > 300){
                flushWriteTimes = 0;
                loggerWriter.flush();
            }

            prevTimeMillis = timeMillis;
        } catch (IOException e) {
            LogLog.error("输出日志有错, 日志如下", e);
            LogLog.error( log );
        }


    }

    public long getTotalWriteTimes() {
        return totalWriteTimes;
    }

    private void gotoNextPeriod( boolean logfileReachMaxSize ){

        //1. 取得当前日志文件最后的修改时间，这一步必须在第二步前做
        long fileLastModified = 0;
        if( lastCreateLogTime > 0  ){
            fileLastModified = lastCreateLogTime;
        }else {
            File oldFile = new File(this.fileName);
            if (oldFile.exists()) {
                fileLastModified = oldFile.lastModified();
            }
        }

        //2. 如果当前writer不为空, 保存并关闭当前writer
        if( loggerWriter != null ){
            try {
                loggerWriter.flush();
                loggerWriter.close();
                loggerWriter = null;
            } catch (IOException e) {
                LogLog.error("gotoNextPeriod() loggerWriter.close, error",  e );
            }
        }


        //3. 计算下一个周期的开始时间
        Date now = new Date();
        nextDayStartMills = calendar.getNextCheckMillis( now );


        //4. 如果文件最后修改时间, 在当天前, 重命名到它所在的时间
        // 如果是大小达到重置大小
        long curDayStartMills = nextDayStartMills - DAY_TOTAL_MSEC;
        boolean needRollingLogfile = (  fileLastModified > 0  &&  fileLastModified < curDayStartMills ) || logfileReachMaxSize;
        if( needRollingLogfile ){

            SimpleDateFormat format =  new  SimpleDateFormat( dateFormat );
            String dateStr = format.format( new Date( fileLastModified ) );
            String renameFileName = String.format(fileNamePattern, dateStr);
            File renameFile = new File( renameFileName );
            if( renameFile.exists() ){

                boolean hasRenamed = false;

                //4.1 先按顺序号重命名
                for( int i = 1; i < DEFAULT_MAX_LOG_SEQ_NO; i++ ){
                    renameFile = new File( renameFileName + "." + i );
                    if( ! renameFile.exists() ){
                        new File(this.fileName).renameTo(renameFile);
                        hasRenamed = true;
                        LogLog.error( "老日志文件按顺序重命名为:" + renameFile.getName() );
                        break;
                    }
                }

                //4.2 如果没有被重命名, 将以随机名字进行重命名
                if( ! hasRenamed ){
                    LogLog.error( "日志文件名1-10000段所有的名字都被占用,将以随机方式创建名字保存" );

                    // 这里就不用while循环判断随机日志名字是否重复啦, 如果1-10000的名字都被占用, 随机名字也冲突
                    // 那就直接覆盖，自认倒霉，加while循环搞不好会死循环的

                    Random random = new Random();
                    renameFile = new File( renameFileName + ".random." + random.nextLong() );
                    new File(this.fileName).renameTo(renameFile);
                    hasRenamed = true;
                    LogLog.error( "老日志文件被重命名为:" + renameFile.getName() );
                }

            }else{
                new File(this.fileName).renameTo(renameFile);
                LogLog.error("老日志文件被重命名为:" + renameFile.getName());
            }
        }



        //5. 打开现有的日志文件，或者新创建日志
        File f = new File( fileName );
        if( ! f.exists() ){
            try {

                if ( ensureHasLogDir(f) ) {
                    f.createNewFile();
                    lastCreateLogTime = System.currentTimeMillis();
                }else{
                    LogLog.error( "创建日志目录失败:" + f.getAbsolutePath() );
                    return;
                }

            } catch (IOException e) {
                LogLog.error("创建日志文件失败:" + f.getAbsolutePath(), e );
                return;
            }
        }

        //6. 创建fileWriter
        try {
            loggerWriter = new FileWriter( f, true );

            //记录文件写入大小
            totalLogSize = f.length();
        } catch (IOException e) {
            LogLog.error("创建loggerWriter失败:" + f.getAbsolutePath(), e );
            return;
        }

    }


    /**
     * 确保日志文件所在文件夹存在, 如果不存在创建好
     * @param f
     * @return
     */
    private boolean ensureHasLogDir(File f) {
        int index = f.getAbsolutePath().lastIndexOf( File.separator );

        // index == 0 代表根目录，也是可以的
        if( index > 0 ) {
            File dir = new File(f.getAbsolutePath().substring(0, index));
            if (!dir.exists()) {
                boolean createDirOK = dir.mkdirs();
                if (!createDirOK) {
                    LogLog.error("创建日志目录失败:" + dir.getAbsolutePath());
                    return false;
                }
            }
        }
        return true;
    }

}
